Skip to content

feat: Add identity federation support#1287

Merged
atheriel merged 1 commit intomainfrom
identity-federation
Jan 23, 2026
Merged

feat: Add identity federation support#1287
atheriel merged 1 commit intomainfrom
identity-federation

Conversation

@atheriel
Copy link
Contributor

@atheriel atheriel commented Jan 14, 2026

This commit teaches rsconnect to automatically exchange a Posit Workbench user's identity token (if there is one) for an ephemeral Connect API key using the server's token exchange endpoint.

This enables Workbench users to authenticate without the need to configure API keys or go through the token auth flow, provided that identity federation has been configured on the Connect server side.

All of this is opportunistic, falling back to existing paths if there is no token or if the Connect server won't accept it.

The majority of the complexity in the implementation is due to the fact that we want to be as backward compatible with RStudio as possible, and RStudio very much expects to run the user through the token auth flow.

Finally, note that we're using the brand new rstudioapi::getIdentityToken() here, so this needs a version bump for the rstudioapi dependency.

Unit tests are included.

Part of https://github.com/posit-dev/connect/issues/28149.

@atheriel atheriel force-pushed the identity-federation branch 2 times, most recently from 3517a65 to f26af60 Compare January 19, 2026 20:30
@atheriel atheriel marked this pull request as ready for review January 19, 2026 20:33
@atheriel atheriel force-pushed the identity-federation branch from f26af60 to e86d80a Compare January 20, 2026 15:04
token = cachedApiKey,
private_key = secret(""),
# Open a special "you're already authenticated" page as the "claim URL".
claim_url = sub("/__api__$", "/connect/#/auth-success", serverUrl),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just confirming we know that serverUrl does not have a trailing slash?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I thought the convention was that the success page was served by localhost, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure that that URL is normalized beforehand, yes.

And as for a locally-served page: we could do that, but it's dramatically more complicated and would require additional dependencies. I added this static success page to the Connect server this week instead.

# Attempt exchange an identity token sourced from Posit Workbench for an
# ephemeral Connect API key. Returns NULL if this exchange fails or an API key
# otherwise.
attemptIdentityFederation <- function(serverUrl) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can trace through the R code to see where the argument comes from, but where exactly does serverUrl originate? Is it pulling from a workbench setting in this case? Or does the user have to know and specify the Connect server URL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They have to know it, though it does get validated in various places so internally at this point we can be sure it's an actual Connect URL.

@atheriel atheriel force-pushed the identity-federation branch from e86d80a to fe95320 Compare January 20, 2026 16:44
This commit teaches rsconnect to automatically exchange a Posit
Workbench user's identity token (if there is one) for an ephemeral
Connect API key using the server's token exchange endpoint.

This enables Workbench users to authenticate without the need to
configure API keys or go through the token auth flow, provided that
identity federation has been configured on the Connect server side.

All of this is opportunistic, falling back to existing paths if there is
no token or if the Connect server won't accept it.

The majority of the complexity in the implementation is due to the fact
that we want to be as backward compatible with RStudio as possible, and
RStudio very much expects to run the user through the token auth flow.

Finally, note that we're using the brand new
`rstudioapi::getIdentityToken()` here, so this needs a version bump for
the `rstudioapi` dependency.

Unit tests are included.

Part of posit-dev/connect#28149.

Signed-off-by: Aaron Jacobs <aaron.jacobs@posit.co>
@atheriel atheriel force-pushed the identity-federation branch from fe95320 to 951eb40 Compare January 23, 2026 16:50
}

# For standard Connect servers where there are no persisted credentials, try
# identity federation (added in v2026.01.0).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be any value here in trying to determine the version of the connect server before attempting identity federation? I guess the end result is the same (no successful auth) - and maybe its more complicated to try to determine the server version vs just relying on sensible error handling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of felt like that would complicate things, especially because we also don't know if identity federation is actually enabled on this server or whether it has an integration configured for Workbench. Not a lot to be gained by being clever about just the version.

private_key = NULL,
apiKey = NULL
) {
# Detect when the "token" is actually an API key by looking for an empty

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know you called out why this is required above - but it remains unfortunate :(

@zackverham
Copy link

I'm not an R expert, but to me this all makes sense and looks good. Especially given that @atheriel has been battle-testing this for awhile now, and that it has solid test coverage, I think it makes sense to merge.

@atheriel atheriel merged commit e3e75bf into main Jan 23, 2026
18 of 19 checks passed
@atheriel atheriel deleted the identity-federation branch January 23, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants